﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Threading;

namespace Xceed.Silverlight.Data
{
  internal class ConcurrentEnumerator : AsyncParentEnumerator<DataPath>
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal ConcurrentEnumerator( 
      SynchronizationContext synchronizationContext,
      IAsyncEnumerator childEnumerator )
      : base( childEnumerator )
    {
      if( synchronizationContext == null )
        throw new ArgumentNullException( "synchronizationContext" );

      m_synchronizationContext = synchronizationContext;
    }

    #endregion

    #region IsFullySynchronized Property

    public sealed override bool IsFullySynchronized
    {
      get
      {
        return false;
      }
    }

    #endregion

    public override IAsyncResult BeginCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      TaskAsyncResult<CountParameters, IAsyncResult, CountResult> asyncResult =
        new TaskAsyncResult<CountParameters, IAsyncResult, CountResult>(
          m_synchronizationContext,
          base.BeginCount,
          base.EndCount,
          userCallback,
          asyncState );

      asyncResult.Execute( parameters );

      return asyncResult;
    }

    public override CountResult EndCount( IAsyncResult asyncResult )
    {
      TaskAsyncResult<CountParameters, IAsyncResult, CountResult> taskAsyncResult = ( TaskAsyncResult<CountParameters, IAsyncResult, CountResult> )asyncResult;
      CountResult result = taskAsyncResult.Result;

      taskAsyncResult.Dispose();

      return result;
    }

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      JumpMoveAndFetchAsyncResult jumpAsyncResult = new JumpMoveAndFetchAsyncResult( ConcurrentEnumerator.JumpMoveAndFetchCallId, userCallback, asyncState, parameters );
      Func<JumpMoveAndFetchParameters, AsyncCallback, object, IAsyncResult> begin = base.BeginJumpMoveAndFetch;
      Func<JumpMoveAndFetchAsyncResult, JumpMoveAndFetchResult<DataPath>> end = base.EndJumpMoveAndFetch;

      jumpAsyncResult.Start(
        ( cb, st ) =>
        {
          TaskAsyncResult<JumpMoveAndFetchParameters, JumpMoveAndFetchAsyncResult, JumpMoveAndFetchResult<DataPath>> asyncResult =
            new TaskAsyncResult<JumpMoveAndFetchParameters, JumpMoveAndFetchAsyncResult, JumpMoveAndFetchResult<DataPath>>(
              m_synchronizationContext,
              begin,
              end,
              cb,
              st );

          asyncResult.Execute( parameters );

          return asyncResult;
        },
        ( par, iar ) =>
        {
          TaskAsyncResult<JumpMoveAndFetchParameters, JumpMoveAndFetchAsyncResult, JumpMoveAndFetchResult<DataPath>> taskAsyncResult = ( TaskAsyncResult<JumpMoveAndFetchParameters, JumpMoveAndFetchAsyncResult, JumpMoveAndFetchResult<DataPath>> )iar;
          JumpMoveAndFetchResult<DataPath> result = taskAsyncResult.Result;

          taskAsyncResult.Dispose();

          return new DependentCallResult( result );
        } );

      return jumpAsyncResult;
    }

    public override JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      jumpMoveAndFetchAsyncResult.ValidateCallEnd( ConcurrentEnumerator.JumpMoveAndFetchCallId );

      return jumpMoveAndFetchAsyncResult.GetCallResult<JumpMoveAndFetchResult<DataPath>>();
    }

    public override IAsyncResult BeginGroupCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      TaskAsyncResult<CountParameters, IAsyncResult, GroupCountResult> asyncResult =
        new TaskAsyncResult<CountParameters, IAsyncResult, GroupCountResult>(
          m_synchronizationContext,
          base.BeginGroupCount,
          base.EndGroupCount,
          userCallback,
          asyncState );

      asyncResult.Execute( parameters );

      return asyncResult;
    }

    public override GroupCountResult EndGroupCount( IAsyncResult asyncResult )
    {
      TaskAsyncResult<CountParameters, IAsyncResult, GroupCountResult> taskAsyncResult = ( TaskAsyncResult<CountParameters, IAsyncResult, GroupCountResult> )asyncResult;
      GroupCountResult result = taskAsyncResult.Result;

      taskAsyncResult.Dispose();

      return result;
    }

    public override IAsyncResult BeginGetStatValues( StatParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      TaskAsyncResult<StatParameters, IAsyncResult, StatResults> asyncResult =
        new TaskAsyncResult<StatParameters, IAsyncResult, StatResults>(
          m_synchronizationContext,
          base.BeginGetStatValues,
          base.EndGetStatValues,
          userCallback,
          asyncState );

      asyncResult.Execute( parameters );

      return asyncResult;
    }

    public override StatResults EndGetStatValues( IAsyncResult asyncResult )
    {
      TaskAsyncResult<StatParameters, IAsyncResult, StatResults> taskAsyncResult = ( TaskAsyncResult<StatParameters, IAsyncResult, StatResults> )asyncResult;
      StatResults result = taskAsyncResult.Result;

      taskAsyncResult.Dispose();

      return result;
    }

    protected override void ChildDataChanged( DataChangedEventArgs args )
    {
      if( m_disposed )
        return;

      SendOrPostCallback d = ( st ) => base.ChildDataChanged( ( DataChangedEventArgs )st );
      m_synchronizationContext.Send( d, args );
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;
      m_synchronizationContext = null;

      base.Dispose( disposing );
    }

    #region Private Fields

    private SynchronizationContext m_synchronizationContext;
    private bool m_disposed; //false

    #endregion

    #region TaskAsyncResult Nested Type

    private class TaskAsyncResult<TParameters, TAsyncResult, TResult> : IAsyncResult, IDisposable
    {
      internal TaskAsyncResult( 
        SynchronizationContext synchronizationContext,
        Func<TParameters, AsyncCallback, object, IAsyncResult> beginMethod,
        Func<TAsyncResult, TResult> endMethod,
        AsyncCallback callback, 
        object asyncState )
      {
        if( synchronizationContext == null )
          throw new ArgumentNullException( "synchronizationContext" );

        if( beginMethod == null )
          throw new ArgumentNullException( "beginMethod" );

        if( endMethod == null )
          throw new ArgumentNullException( "endMethod" );

        m_synchronizationContext = synchronizationContext;
        m_beginMethod = beginMethod;
        m_endMethod = endMethod;
        m_callback = callback;
        m_asyncState = asyncState;
      }

      public object AsyncState
      {
        get
        {
          return m_asyncState;
        }
      }

      public WaitHandle AsyncWaitHandle
      {
        get
        {
          if( m_waitHandle == null )
          {
            m_waitHandle = new ManualResetEvent( m_isCompleted );
          }

          return m_waitHandle;
        }
      }

      public bool IsCompleted
      {
        get
        {
          return m_isCompleted;
        }
      }

      public bool CompletedSynchronously
      {
        get
        {
          return m_completedSynchronously;
        }
      }

      internal TResult Result
      {
        get
        {
          if( !m_isCompleted )
          {
            this.AsyncWaitHandle.WaitOne();
          }

          if( m_error != null )
            throw m_error;

          return m_result;
        }
      }

      internal Exception Error
      {
        get
        {
          if( !m_isCompleted )
          {
            this.AsyncWaitHandle.WaitOne();
          }

          return m_error;
        }
      }

      public void Dispose()
      {
        if( m_waitHandle != null )
        {
          m_waitHandle.Dispose();
        }
      }

      internal void Execute( TParameters parameter )
      {
        WaitCallback worker = ( st ) => m_beginMethod.Invoke( parameter, this.ExecuteCallback, st );
        ThreadPool.QueueUserWorkItem( worker, this );

        m_completedSynchronously = this.IsCompleted;
      }

      private void ExecuteCallback( IAsyncResult asyncResult )
      {
        TaskAsyncResult<TParameters, TAsyncResult, TResult> ar = ( TaskAsyncResult<TParameters, TAsyncResult, TResult> )asyncResult.AsyncState;
        TResult result;
        Exception error;

        try
        {
          result = m_endMethod.Invoke( ( TAsyncResult )asyncResult );
          error = null;
        }
        catch( Exception e )
        {
          result = default( TResult );
          error = e;
        }

        if( error != null )
        {
          ar.SetError( error );
        }
        else
        {
          ar.SetResult( result );
        }
      }

      private void SetResult( TResult result )
      {
        if( m_isCompleted )
          throw new InvalidOperationException();

        m_result = result;

        this.Complete();
      }

      private void SetError( Exception error )
      {
        if( error == null )
          throw new ArgumentNullException( "error" );

        if( m_isCompleted )
          throw new InvalidOperationException();

        m_error = error;

        this.Complete();
      }

      private void Complete()
      {
        if( m_callback != null )
        {
          // Since the callback will be dispatched, we may consider the 
          // result to be asynchronous.
          m_completedSynchronously = false;
        }

        m_isCompleted = true;

        if( m_waitHandle != null )
        {
          m_waitHandle.Set();
        }

        if( m_callback != null )
        {
          SendOrPostCallback d = ( st ) => m_callback.Invoke( ( IAsyncResult )st );

          m_synchronizationContext.Post( d, this );
        }
      }

      private readonly SynchronizationContext m_synchronizationContext;
      private readonly AsyncCallback m_callback;
      private readonly object m_asyncState;
      private readonly Func<TParameters, AsyncCallback, object, IAsyncResult> m_beginMethod;
      private readonly Func<TAsyncResult, TResult> m_endMethod;
      private bool m_isCompleted; //false
      private bool m_completedSynchronously = true;
      private ManualResetEvent m_waitHandle = null;
      private TResult m_result;
      private Exception m_error;
    }

    #endregion
  }
}
